package model;

import java.math.BigInteger;

public class BigFraction {

	private static final String FRACTION_SEPERATOR = "/";
	
	/**
	 * The BigFraction constant zero.
	 */
	public static final BigFraction ZERO = BigFraction.create(BigInteger.ZERO,
			BigInteger.ONE);
	
	/**
	 * The BigFraction constant one.
	 */
	public static final BigFraction ONE = BigFraction.create(BigInteger.ONE, BigInteger.ONE);

	/**
	 * Creates a new Fraction with <enumerator> and <denominator>.
	 * 
	 * @throws Error
	 *             if the denominator is zero.
	 * @return a normalized fraction.
	 */
	public static BigFraction create(BigInteger enumerator,
			BigInteger denominator) {
		if (denominator.equals(BigInteger.ZERO))
			throw new Error("Denominator shouldn't be zero.");

		final BigInteger normalizer = denominator.abs().equals(denominator) ? BigInteger.ONE
				: BigInteger.ONE.negate();

		final BigInteger gcd = enumerator.gcd(denominator);
		return new BigFraction(enumerator.multiply(normalizer).divide(gcd),
				denominator.abs().divide(gcd));
	}

	/**
	 * Parses <str> to a BigFraction Object.
	 * 
	 * @throws NumberFormatException
	 *             if the given String is no representation of a BigFraction
	 * @return the given String as BigFraction
	 */
	public static BigFraction parse(String str) {
		if (str.contains(FRACTION_SEPERATOR)) {
			final String[] parts = str.split(FRACTION_SEPERATOR);
			if (parts.length != 2)
				throw new NumberFormatException();
			return BigFraction.create(new BigInteger(parts[0]), new BigInteger(
					parts[1]));
		}
		throw new NumberFormatException();
	}

	private final BigInteger enumerator;
	private final BigInteger denominator;

	private BigFraction(BigInteger enumerator, BigInteger denominator) {
		this.enumerator = enumerator;
		this.denominator = denominator;
	}

	/**
	 * Adds <a> to <this> and returns the result as a new object.
	 */
	public BigFraction add(BigFraction a) {
		final BigInteger enumerator = this.getEnumerator()
				.multiply(a.getDenominator())
				.add(this.getDenominator().multiply(a.getEnumerator()));
		final BigInteger denominator = this.getDenominator().multiply(
				a.getDenominator());
		return BigFraction.create(enumerator, denominator);
	}

	/**
	 * Subtracts <a> from <this> and returns the result as a new object.
	 */
	public BigFraction subtract(BigFraction a) {
		final BigInteger enumerator = this.getEnumerator()
				.multiply(a.getDenominator())
				.subtract(this.getDenominator().multiply(a.getEnumerator()));
		final BigInteger denominator = this.getDenominator().multiply(
				a.getDenominator());
		return BigFraction.create(enumerator, denominator);
	}

	/**
	 * Multiplys <a> and <this> and returns the result as a new object.
	 */
	public BigFraction multiply(BigFraction a) {
		final BigInteger enumerator = this.getEnumerator().multiply(
				a.getEnumerator());
		final BigInteger denominator = this.getDenominator().multiply(
				a.getDenominator());
		return BigFraction.create(enumerator, denominator);
	}

	/**
	 * Divides <this> by <a> and returns the result as a new object.
	 * 
	 * @throws IllegalArgumentException
	 *             if <a> equals zero.
	 */
	public BigFraction divide(BigFraction a) {
		if (a.equals(BigFraction.ZERO))
			throw new IllegalArgumentException("Divisor shouldn't be zero.");
		final BigInteger enumerator = this.getEnumerator().multiply(
				a.getDenominator());
		final BigInteger denominator = this.getDenominator().multiply(
				a.getEnumerator());
		return BigFraction.create(enumerator, denominator);
	}

	/**
	 * Returns the enumerator of <this>.
	 */
	private BigInteger getEnumerator() {
		return enumerator;
	}

	/**
	 * Returns the denominator of <this>.
	 */
	private BigInteger getDenominator() {
		return denominator;
	}

	/**
	 * Returns true if <argument> and <this> have the same value otherwise
	 * false.
	 */
	@Override
	public boolean equals(Object argument) {
		if (argument instanceof BigFraction) {
			final BigFraction argumentAsBigFraction = (BigFraction) argument;
			return (this.getEnumerator().multiply(argumentAsBigFraction
					.getDenominator())) == (this.getDenominator()
					.multiply(argumentAsBigFraction.getEnumerator()));
		} else {
			return false;
		}
	}

	/**
	 * Stringifies <this>.
	 */
	@Override
	public String toString() {
		return this.getEnumerator() + FRACTION_SEPERATOR
				+ this.getDenominator();
	}
}
